package com.vcg.vdam.config.redislimit;

import com.google.common.util.concurrent.RateLimiter;
import com.vcg.vdam.redisLock.DistributedLock;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @ClassName RateLimitAop
 * @DescripTion
 * @Author yangxing
 * @Date 2020/9/7 11:20
 **/
@Aspect
@Component
public class RateLimitAop {

    public final ConcurrentHashMap<String, RateLimiter> concurrentHashMap = new ConcurrentHashMap<>();

    //通过木桶法则来实现限流
    public final ConcurrentHashMap<String, List<Long>> woodMap = new ConcurrentHashMap<>();

    public final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    @Autowired
    private DistributedLock distributedLock;

    @Pointcut("@annotation(com.vcg.vdam.config.redislimit.RateLimit)")
    public void webLog() {
    }

    //通过ratelimit控制限流；但是有风险；就是使用lock锁的地方；只锁一部分；别锁业务代码；procedd();
    //后期可以把acquire改成tryacquire；这样可以等待对应的时间；如果时间内没有获取到令牌就报错
    @Around("webLog()")
    public Object doAroud(ProceedingJoinPoint joinPoint) throws Throwable {
        Object obj = null;
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        String url = request.getRequestURL().toString();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        RateLimit rateLimitAnnotation = signature.getMethod().getAnnotation(RateLimit.class);
        long limit = rateLimitAnnotation.limit();
        long refreshInterval = rateLimitAnnotation.refreshInterval();
        double f1 = new BigDecimal((float) limit / refreshInterval).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();

        //通过谷歌的ratelimit进行限流操作
        try {
            lock.writeLock().lock();
            RateLimiter rateLimiter = concurrentHashMap.get(url);

            if (rateLimiter != null) {

                //代表已经开始限流了
                //获取到令牌需要多长时间
                //这里不需要加锁；因为ratelimiter已经帮我们实现了这个顺序和安全
                System.out.println("1获取到令牌需要多长时间" + rateLimiter.acquire());

            } else {
                //代表没有限流过
                //需要加锁；如果两个线程进来之后；会创建两个不同的rateLimiter对象
                //因为维度在每个方法上；所以如果用locK锁；没办法保证每个方法上一个lock锁；所以用分布式锁


                //如果用redis锁；会把几台机器同事锁住；需要添加服务器ip区分
                //while(!distributedLock.lock(url, 10000L, 3,1000)){
                //    System.out.println("没有获取到锁一直循环");
                //};

                //后期可以把acquire改成tryacquire；这样可以等待对应的时间；如果时间内没有获取到令牌就报错
                rateLimiter = RateLimiter.create(f1);
                concurrentHashMap.put(url, rateLimiter);
                System.out.println("2获取到令牌需要多长时间" + rateLimiter.acquire());


            }
        } catch (Exception e) {
            e.printStackTrace();
            return "限流失败";
        } finally {
            lock.writeLock().unlock();
        }
        obj = joinPoint.proceed();
        return obj;
    }

    //通过木桶法则来限流；
    //@Around("webLog()")
    //public Object doAroud(ProceedingJoinPoint joinPoint) throws Throwable {
    //    Object obj = null;
    //    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    //    HttpServletRequest request = attributes.getRequest();
    //
    //    String url = request.getRequestURL().toString();
    //    MethodSignature signature = (MethodSignature) joinPoint.getSignature();
    //    RateLimit rateLimitAnnotation = signature.getMethod().getAnnotation(RateLimit.class);
    //    long limit = rateLimitAnnotation.limit();
    //    long refreshInterval = rateLimitAnnotation.refreshInterval();
    //    double f1 = new BigDecimal((float)limit/refreshInterval).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
    //
    //    //int limitSizeRate = new Double(f1).intValue();
    //    //开始限流
    //
    //    //添加锁保证线程安全
    //    lock.writeLock().lock();
    //    List<Long> longsCurrentTimeMillis = null;
    //    long currentTimeMillis = System.currentTimeMillis();
    //    try {
    //        longsCurrentTimeMillis = woodMap.get(url);
    //
    //        if (CollectionUtils.isEmpty(longsCurrentTimeMillis)) {
    //            //代表没有限流过
    //            List<Long> longs = new ArrayList<Long>();
    //            longs.add(currentTimeMillis);
    //            woodMap.put(url, longs);
    //        } else {
    //            int size = longsCurrentTimeMillis.size();
    //            if (size >= limit) {
    //                //判断当前时间和第一时间之差是否大于间隔时间；
    //
    //                if ((currentTimeMillis - longsCurrentTimeMillis.get(0)) / 1000L > refreshInterval) {
    //                    //代表符合要求；不需要限流
    //                    longsCurrentTimeMillis.remove(0);
    //                    longsCurrentTimeMillis.add(currentTimeMillis);
    //                } else {
    //                    //代表需要限流；直接pass掉
    //
    //                    return "方法" + url + "超过限流规则";
    //                }
    //            } else {
    //                longsCurrentTimeMillis.add(System.currentTimeMillis());
    //            }
    //        }
    //    } catch (Exception e) {
    //        e.printStackTrace();
    //    } finally {
    //        lock.writeLock().unlock();
    //    }
    //
    //    obj = joinPoint.proceed();
    //
    //
    //    return obj;
    //}
}
